package com.yf.limiter.impl;

import com.yf.limiter.RateLimiter;

import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 滑动窗口计数器（终极正确版）
 * 核心改进：显式维护「时间起始点+格子起始点」，窗口范围一目了然
 * 解决痛点：明确窗口从哪个格子开始，过期清理精准，逻辑无歧义
 */
public class SlidingWindowLimiter<T> implements RateLimiter<T> {
    // 总窗口大小（ms）
    private final long windowMs;
    // 小格子数量
    private final int gridCount;
    // 每个格子大小（ms）
    private final long gridMs;
    // 最大请求数
    private final int maxRequests;
    // 格子计数器
    private final AtomicIntegerArray gridCounters;

    // 窗口的开始时间（ms）
    private volatile long windowStartTime;
    // 当前窗口的起始索引
    private volatile int windowStartGridIndex;

    private final Lock lock = new ReentrantLock(false);

    public SlidingWindowLimiter(long windowMs, int gridCount, int maxRequests) {
        this.windowMs = windowMs;
        this.gridCount = gridCount;
        this.gridMs = windowMs / gridCount;
        this.maxRequests = maxRequests;
        this.gridCounters = new AtomicIntegerArray(gridCount);

        long now = System.currentTimeMillis();
        this.windowStartTime = now - (now % gridMs);
        this.windowStartGridIndex = (int) ((windowStartTime / gridMs) % gridCount);
    }

    /**
     * 尝试获取请求许可（核心入口，逻辑绝对清晰）
     * @return true-允许，false-限流
     */
    public boolean submit(T req) {
        long now = System.currentTimeMillis();
        // 无锁计算当前请求对应的格子索引（基于窗口格子起始点，无需间接推导）
        int currentGridIndex = getCurrentGridIndex(now);

        // 细粒度锁：仅保护窗口推进、清理、求和（原子操作）
        int totalRequests = 0;
        lock.lock();
        try {
            // 1. 推进窗口到当前时间（同步更新时间+格子双起始点）
            advanceWindow(now);
            // 2. 计算当前窗口内的总请求数（基于明确的格子范围）
            totalRequests = getTotalRequests();
        } finally {
            lock.unlock();
        }

        // 3. 无锁加1：原子操作，单个格子并发安全
        if (totalRequests < maxRequests) {
            gridCounters.incrementAndGet(currentGridIndex);
            return true;
        }
        return false;
    }

    /**
     * 无锁计算当前时间对应的格子索引（逻辑直观，基于窗口格子起始点）
     */
    private int getCurrentGridIndex(long now) {
        // 1. 计算当前时间相对于窗口起始时间的偏移量（ms）
        long offsetMs = now - windowStartTime;
        // 2. 计算偏移了多少个格子（整数，无偏差）
        long offsetGrids = offsetMs / gridMs;
        // 3. 当前格子索引 = 窗口起始格子 + 偏移格子数 → 循环复用
        return (int) ((windowStartGridIndex + offsetGrids) % gridCount);
    }


    private void advanceWindow(long now) {
        // 计算窗口需要推进的总时间：当前时间 - 窗口结束时间（windowStartTime + windowMs）
        long needAdvanceMs = now - (windowStartTime + windowMs);
        if (needAdvanceMs <= 0) {
            return; // 未超过当前窗口，无需推进
        }

        // 计算需要推进的格子数（整数，无偏差）
        long advanceGrids = needAdvanceMs / gridMs;
        // 计算新的窗口时间起始点（推进 advanceGrids 个格子的时间）
        long newWindowStartTime = windowStartTime + advanceGrids * gridMs;
        // 计算新的窗口格子起始点（推进 advanceGrids 个格子，循环复用）
        int newWindowStartGridIndex = (int) ((windowStartGridIndex + advanceGrids) % gridCount);

        // 清理过期格子：所有在「旧窗口起始点 ~ 新窗口起始点之前」的格子
        cleanExpiredGrids(advanceGrids);

        // 同步更新双起始点（volatile保证多线程可见性）
        this.windowStartTime = newWindowStartTime;
        this.windowStartGridIndex = newWindowStartGridIndex;
    }


    private void cleanExpiredGrids(long advanceGrids) {
        // 若推进的格子数≥总格子数，说明所有格子都过期，直接批量重置
        if (advanceGrids >= gridCount) {
            for (int i = 0; i < gridCount; i++) {
                gridCounters.set(i, 0);
            }
            return;
        }

        // 否则，只清理「旧起始点 ~ 推进后的起始点之前」的格子（精准清理）
        int expireGridIndex = windowStartGridIndex;
        for (int i = 0; i < advanceGrids; i++) {
            gridCounters.set(expireGridIndex, 0);
            // 过期格子索引循环推进（到最后一个格子后回到0）
            expireGridIndex = (expireGridIndex + 1) % gridCount;
        }
    }

    /**
     * 窗口内的总请求数
     */
    private int getTotalRequests() {
        int total = 0;
        int currentGridIndex = windowStartGridIndex;
        // 遍历当前窗口的所有格子：从起始格子开始，循环gridCount个格子
        for (int i = 0; i < gridCount; i++) {
            total += gridCounters.get(currentGridIndex);
            currentGridIndex = (currentGridIndex + 1) % gridCount;
        }
        return total;
    }


}
